package com.stone.boot.pipeline.app.service.impl;

import com.stone.boot.pipeline.app.service.PipelineRedisService;
import com.stone.boot.pipeline.domain.entity.Pipeline;
import com.stone.boot.pipeline.domain.entity.PipelineNode;
import com.stone.boot.pipeline.domain.repository.PipelineNodeRepository;
import com.stone.boot.pipeline.domain.repository.PipelineRepository;
import com.stone.boot.pipeline.domain.vo.PipelineNodeVO;
import com.stone.boot.pipeline.domain.vo.PipelineVO;
import com.stone.boot.pipeline.infra.constant.StonePipelineConstant;
import com.stone.starter.core.helper.ApplicationContextHelper;
import com.stone.starter.core.helper.FastJsonHelper;
import com.stone.starter.redis.client.RedisCacheClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 流程器Redis操作实现
 *
 * @author Mr_wenpan@163.com 2021/8/5 10:13 下午
 */
@Service
public class PipelineRedisServiceImpl implements PipelineRedisService {

    private final PipelineNodeRepository pipelineNodeRepository;

    private final PipelineRepository pipelineRepository;

    private static final DefaultRedisScript<Boolean> DEFAULT_REDIS_SCRIPT;

    static {
        DEFAULT_REDIS_SCRIPT = new DefaultRedisScript<>();
        DEFAULT_REDIS_SCRIPT.setScriptSource(StonePipelineConstant.Redis.PIPELINE_CONF_UPDATE_LUA);
        DEFAULT_REDIS_SCRIPT.setResultType(Boolean.class);
    }

    public PipelineRedisServiceImpl(PipelineNodeRepository pipelineNodeRepository,
                                    PipelineRepository pipelineRepository) {
        this.pipelineNodeRepository = pipelineNodeRepository;
        this.pipelineRepository = pipelineRepository;
    }

    @Override
    public void createRedisPipelineNodeConf(Long pipelineId) {
        Pipeline pipeline = pipelineRepository.selectByPrimaryKey(pipelineId);
        List<PipelineNode> pipelineNodes = pipelineNodeRepository.select(PipelineNode.FIELD_PIPELINE_ID, pipelineId);
        String pipelineEntityJson = buildPipelineConfJson(pipeline, pipelineNodes);

        Map<String, String> pipelineEntity = new HashMap<>(2);
        pipelineEntity.put(StonePipelineConstant.Redis.PIPELINE_NODE_INFO, pipelineEntityJson);
        pipelineEntity.put(StonePipelineConstant.Redis.PIPELINE_VERSION, pipeline.getObjectVersionNumber().toString());
        RedisCacheClient redisCacheClient = ApplicationContextHelper.getContext().getBean(RedisCacheClient.class);
        redisCacheClient.opsForHash().putAll(buildInterfaceConfKey(pipeline.getCode()), pipelineEntity);
    }

    @Override
    public boolean saveRedisPipelineNodeConf(Long pipelineId) {
        Pipeline pipeline = pipelineRepository.selectByPrimaryKey(pipelineId);
        List<PipelineNode> pipelineNodes = pipelineNodeRepository.select(PipelineNode.FIELD_PIPELINE_ID, pipelineId);
        String pipelineCode = pipeline.getCode();
        String interfaceEntityJson = buildPipelineConfJson(pipeline, pipelineNodes);
        String pipelineVersion = pipeline.getObjectVersionNumber().toString();

        return executeScript(pipelineCode, interfaceEntityJson, pipelineVersion);
    }

    @Override
    public void delRedisPipelineConf(String pipelineCode) {
        RedisCacheClient redisCacheClient = ApplicationContextHelper.getContext().getBean(RedisCacheClient.class);
        redisCacheClient.expire(buildInterfaceConfKey(pipelineCode),
                StonePipelineConstant.Redis.EXPIRE_TIME_MINUTES, TimeUnit.MINUTES);
    }

    @Override
    public String getPipelineConf(String pipelineConfCode) {
        RedisCacheClient redisCacheClient = ApplicationContextHelper.getContext().getBean(RedisCacheClient.class);
        String interfaceConfJson = redisCacheClient.<String, String>opsForHash()
                .get(buildInterfaceConfKey(pipelineConfCode), StonePipelineConstant.Redis.PIPELINE_NODE_INFO);
        return StringUtils.isBlank(interfaceConfJson) ? "" : interfaceConfJson;
    }

    /**
     * 构建流水线保存到Redis中的json字符串
     */
    private String buildPipelineConfJson(Pipeline pipeline, List<PipelineNode> pipelineNodes) {
        PipelineVO pipelineVO = new PipelineVO();
        pipelineVO.setStartAction(pipeline.getStartAction());
        pipelineVO.setEndAction(pipeline.getEndAction());
        pipelineVO.setPipelineCode(pipeline.getCode());

        for (PipelineNode pipelineNode : pipelineNodes) {
            PipelineNodeVO pipelineNodeVO = new PipelineNodeVO();
            pipelineNodeVO.setCurAction(pipelineNode.getCurAction());
            pipelineNodeVO.setNextAction(pipelineNode.getNextAction());
            pipelineNodeVO.setNodeId(pipelineNode.getId());
            pipelineNodeVO.setStrategyType(pipelineNode.getStrategyType());
            pipelineNodeVO.setCurAction(pipelineNode.getCurAction());
            pipelineNodeVO.setNextAction(pipelineNode.getNextAction());
            // 获取当前节点的动作，并加入到动作集合
            pipelineVO.getAllNodeAction().add(pipelineNodeVO.getCurAction());
            // 将当前节点（spring bean id） + strategyType 组成唯一字符串，然后put到map
            pipelineVO.getPipelineNodes().put(pipelineNodeVO.uniqueKey(), pipelineNodeVO);
        }

        return FastJsonHelper.objectToString(pipelineVO);
    }

    private boolean executeScript(String pipelineCode, String pipelineEntityJson, String pipelineVersion) {
        RedisCacheClient redisCacheClient = ApplicationContextHelper.getContext().getBean(RedisCacheClient.class);
        Boolean execute = redisCacheClient.execute(DEFAULT_REDIS_SCRIPT, Collections.emptyList(),
                buildInterfaceConfKey(pipelineCode), pipelineEntityJson, pipelineVersion);
        return Objects.isNull(execute) ? Boolean.FALSE : execute;
    }

    /**
     * @param pipelineCode 流程器配置代码
     * @return redis cache key
     */
    public String buildInterfaceConfKey(String pipelineCode) {
        return String.format(StonePipelineConstant.Redis.PIPELINE_KEY, pipelineCode);
    }

}